// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ssa

import (
	"cmd/compile/internal/ir"
	"cmd/internal/src"
	"internal/buildcfg"
)

// nilcheckelim消除了不必要的零检查。
// 在独立于机器的代码上运行。
func nilcheckelim(f *Func) {
	// 如果同一个零检查在
	// 主块中成功，则零检查是多余的。这个通行证的有效性在很大程度上取决于
	// cse通行证的有效性。
	sdom := f.Sdom()

	// 待办事项：取消更多的零检查。
	// 我们可以递归删除任何固定偏移量计算链，
	// 即结构字段和数组元素，即使使用非常量
	// 索引：x在x.a.b[i]时为非零。c是。

	type walkState int
	const (
		Work     walkState = iota // 处理零检查并遍历到Domines 
		ClearPtr                  // 忘记ptr为零的事实
	)

	type bp struct {
		block *Block // 块，或ClearPtr状态为零
		ptr   *Value // 如果非零，要在ClearPtr状态下清除的ptr 
		op    walkState
	}

	work := make([]bp, 0, 256)
	work = append(work, bp{block: f.Entry})

	// 从值ID映射到bool，指示当前正在行走的支配路径中的值是否已知为非零
	// 。该片由
	// WalkState更新，以保持已知的非零值。
	nonNilValues := make([]bool, f.NumValues())

	// 进行初始传递，识别任何非零值
	for _, b := range f.Blocks {
		for _, v := range b.Values {
			// 获取
			// 值的地址产生的值，或由
			// 非零ptr（OpAddPtr）的偏移量构造的值，意味着它是非零
			// 我们还假设不安全的指针算法生成非零指针。见#27180。
			// 我们假设SlicePtr是非nil的，因为我们在切片访问之前做了一个边界检查
			// 所有cap>0的切片都有一个非nil的ptr。参见#30366。
			if v.Op == OpAddr || v.Op == OpLocalAddr || v.Op == OpAddPtr || v.Op == OpOffPtr || v.Op == OpAdd32 || v.Op == OpAdd64 || v.Op == OpSub32 || v.Op == OpSub64 || v.Op == OpSlicePtr {
				nonNilValues[v.ID] = true
			}
		}
	}

	for changed := true; changed; {
		changed = false
		for _, b := range f.Blocks {
			for _, v := range b.Values {
				// 参数均为非零
				// 参数均为非零
				if v.Op == OpPhi {
					argsNonNil := true
					for _, a := range v.Args {
						if !nonNilValues[a.ID] {
							argsNonNil = false
							break
						}
					}
					if argsNonNil {
						if !nonNilValues[v.ID] {
							changed = true
						}
						nonNilValues[v.ID] = true
					}
				}
			}
		}
	}

	// 为计算存储顺序分配辅助数据结构
	sset := f.newSparseSet(f.NumValues())
	defer f.retSparseSet(sset)
	storeNumber := make([]int32, f.NumValues())

	// 执行支配树的深度优先遍历
	for len(work) > 0 {
		node := work[len(work)-1]
		work = work[:len(work)-1]

		switch node.op {
		case Work:
			b := node.block

			// 首先，看看我们是否被显式的零检查控制。
			if len(b.Preds) == 1 {
				p := b.Preds[0].b
				if p.Kind == BlockIf && p.Controls[0].Op == OpIsNonNil && p.Succs[0].b == b {
					if ptr := p.Controls[0].Args[0]; !nonNilValues[ptr.ID] {
						nonNilValues[ptr.ID] = true
						work = append(work, bp{op: ClearPtr, ptr: ptr})
					}
				}
			}

			// 接下来，在当前块w.r.t.存储中订购值。
			b.Values = storeOrder(b.Values, sset, storeNumber)

			pendingLines := f.cachedLineStarts // 保存需要移动到新值/块的语句边界
			pendingLines.clear()

			// 接下来，处理块中的值。
			i := 0
			for _, v := range b.Values {
				b.Values[i] = v
				i++
				switch v.Op {
				case OpIsNonNil:
					ptr := v.Args[0]
					if nonNilValues[ptr.ID] {
						if v.Pos.IsStmt() == src.PosIsStmt { // 布尔true是一个糟糕的语句边界。
							pendingLines.add(v.Pos)
							v.Pos = v.Pos.WithNotStmt()
						}
						// 这是一个冗余的显式零检查。
						v.reset(OpConstBool)
						v.AuxInt = 1 // 真
					}
				case OpNilCheck:
					ptr := v.Args[0]
					if nonNilValues[ptr.ID] {
						// 这是一个冗余的隐式零检查。
						// 以前一个编译器的风格登录——并省略第1行，
						// 这通常是在生成的代码中。
						if f.fe.Debug_checknil() && v.Pos.Line() > 1 {
							f.Warnl(v.Pos, "removed nil check")
						}
						if v.Pos.IsStmt() == src.PosIsStmt { // 即将丢失语句边界
							pendingLines.add(v.Pos)
						}
						v.reset(OpUnknown)
						f.freeValue(v)
						i--
						continue
					}
					// 记录我们知道ptr非零的事实，并记住
					// 在完成此支配子树时撤消该信息。
					nonNilValues[ptr.ID] = true
					work = append(work, bp{op: ClearPtr, ptr: ptr})
					fallthrough // 非消除的零检查可能是语句边界的好地方。
				default:
					if v.Pos.IsStmt() != src.PosNotStmt && !isPoorStatementOp(v.Op) && pendingLines.contains(v.Pos) {
						v.Pos = v.Pos.WithIsStmt()
						pendingLines.remove(v.Pos)
					}
				}
			}
			// 这将使“go”中的丢失语句数减少5（总共500个）。这是订购问题吗？
				v := b.Values[j]
				if v.Pos.IsStmt() != src.PosNotStmt && !isPoorStatementOp(v.Op) && pendingLines.contains(v.Pos) {
					v.Pos = v.Pos.WithIsStmt()
					pendingLines.remove(v.Pos)
				}
			}
			if pendingLines.contains(b.Pos) {
				b.Pos = b.Pos.WithIsStmt()
				pendingLines.remove(b.Pos)
			}
			b.truncateValues(i)

			// 将所有主导区块添加到工作列表中。
			for w := sdom[node.block.ID].child; w != nil; w = sdom[w.ID].sibling {
				work = append(work, bp{op: Work, block: w})
			}

		case ClearPtr:
			nonNilValues[node.ptr.ID] = false
			continue
		}
	}
}

// 如果我们加载/存储到任何小于此地址的内容，所有平台都会发生故障。
// 
// 这应该与运行时的minLegalPointer一致。
const minZeroPage = 4096

// 如果加载到minZeroPage以下的地址将触发SIGSEGV，则faultOnLoad为true。
var faultOnLoad = buildcfg.GOOS != "aix"

// nilcheckelim2消除了不必要的零检查。
// 在降低和调度后运行。
func nilcheckelim2(f *Func) {
	unnecessary := f.newSparseMap(f.NumValues()) // 从将被取消引用的指针映射到b.Values[]
	defer f.retSparseMap(unnecessary)

	pendingLines := f.cachedLineStarts // 中取消引用值的索引保持需要移动到新值/块

	for _, b := range f.Blocks {
		// 向后移动块的语句边界。如果指令的
		// 输入指针为零，则查找将出现故障的指令。删除这些指针上的nil检查，因为
		// faulting指令有效地免费执行nil检查。
		unnecessary.clear()
		pendingLines.clear()
		// 优化：使用最小索引跟踪删除的nilcheck 
		firstToRemove := len(b.Values)
		for i := len(b.Values) - 1; i >= 0; i-- {
			v := b.Values[i]
			if opcodeTable[v.Op].nilCheck && unnecessary.contains(v.Args[0].ID) {
				if f.fe.Debug_checknil() && v.Pos.Line() > 1 {
					f.Warnl(v.Pos, "removed nil check")
				}
				// 对于错误33724，策略是我们可能会选择将现有位置
				// 从故障加载/存储中删除，以支持来自nil check的位置。

				// 迭代顺序意味着链中的第一个nilcheck获胜，其他
				// 被碰撞到普通的语句保存算法中。
				u := b.Values[unnecessary.get(v.Args[0].ID)]
				if !u.Pos.SameFileAndLine(v.Pos) {
					if u.Pos.IsStmt() == src.PosIsStmt {
						pendingLines.add(u.Pos)
					}
					u.Pos = v.Pos
				} else if v.Pos.IsStmt() == src.PosIsStmt {
					pendingLines.add(v.Pos)
				}

				v.reset(OpUnknown)
				firstToRemove = i
				continue
			}
			if v.Type.IsMemory() || v.Type.IsTuple() && v.Type.FieldType(1).IsMemory() {
				if v.Op == OpVarKill || v.Op == OpVarLive || (v.Op == OpVarDef && !v.Aux.(*ir.Name).Type().HasPointers()) {
					// 这些操作不会真正改变内存。
					continue
					// 注意：OpVarDef要求定义的变量没有指针。
					// 我们需要确保VarDef和完全初始化的变量
					// 指令。如果有，那么任何扫描
					// 之间没有可能的错误
					// 在处理该故障期间，堆栈将在堆栈上看到
					// 一个活动但未初始化的指针变量。
					// 
					// 如果我们有：
					// 
					// NilCheck p 
					// VarDef x 
					// x=*p 
					// 
					// 我们不能将其改写为
					// 
					// VARDEFG x 
					// 见第32288期。
				}
				// 此操作会更改内存。v之后的任何错误指令
				// 我们在不必要的映射中记录的指令现在都已过时。
				unnecessary.clear()
			}

			// 如果为nil，则查找此op保证会出错的任何指针。
			var ptrstore [2]*Value
			ptrs := ptrstore[:0]
			if opcodeTable[v.Op].faultOnNilArg0 && (faultOnLoad || v.Type.IsMemory()) {
				// 在AIX上，只有写入才会出错。
				ptrs = append(ptrs, v.Args[0])
			}
			if opcodeTable[v.Op].faultOnNilArg1 && (faultOnLoad || (v.Type.IsMemory() && v.Op != OpPPC64LoweredMove)) {
				// 在AIX上，只有写入才会出错。
				// LowerdMove是一种特殊情况，因为它被视为“mem”，因为它存储在arg0上，但arg1是作为负载访问的，应该进行检查。
				ptrs = append(ptrs, v.Args[1])
			}

			for _, ptr := range ptrs {
				// 检查以确保偏移量较小。
				switch opcodeTable[v.Op].auxType {
				case auxSym:
					if v.Aux != nil {
						continue
					}
				case auxSymOff:
					if v.Aux != nil || v.AuxInt < 0 || v.AuxInt >= minZeroPage {
						continue
					}
				case auxSymValAndOff:
					off := ValAndOff(v.AuxInt).Off()
					if v.Aux != nil || off < 0 || off >= minZeroPage {
						continue
					}
				case auxInt32:
					// Mips将此auxType用于原子加法常量。它不影响有效地址。
				case auxInt64:
					// ARM将此auxType用于duffcopy/duffzero/alignment info。
					// 不影响有效地址。
				case auxNone:
					// 偏移量为零。
				default:
					v.Fatalf("can't handle aux %s (type %d) yet\n", v.auxString(), int(opcodeTable[v.Op].auxType))
				}
				// 如果ptr为零，则此指令保证出错。
				// 之前的任何零检查操作都是不必要的。
				unnecessary.set(ptr.ID, int32(i), src.NoXPos)
			}
		}
		// 删除我们用OpUnknown删除的值。
		i := firstToRemove
		for j := i; j < len(b.Values); j++ {
			v := b.Values[j]
			if v.Op != OpUnknown {
				if !notStmtBoundary(v.Op) && pendingLines.contains(v.Pos) { // 编译较晚，所以现在任何剩余的NOTSMT值都可能没有问题。
					v.Pos = v.Pos.WithIsStmt()
					pendingLines.remove(v.Pos)
				}
				b.Values[i] = v
				i++
			}
		}

		if pendingLines.contains(b.Pos) {
			b.Pos = b.Pos.WithIsStmt()
		}

		b.truncateValues(i)

		// TODO:如果b.Kind==BlockPlain，则在后续块中开始分析，以找到
		// 更多不必要的零检查。将修复test/nilptr3。go：159。
	}
}
